using System;
using System.Collections.Generic;
using UnityEngine;


namespace BF
{
    public class MonoPool<T> : IPool<T> where T : MonoRecycleItem
    {
        public readonly string tName;

        private static MonoPool<T> _instance;
        public static MonoPool<T> instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new MonoPool<T>();
                }
                return _instance;
            }
        }

        protected class MPool<TM> : BasePool<TM> where TM : MonoRecycleItem
        {
            protected override float ITEM_FREEZE_TIME => 0.05f;

            public GameObject tempPrefab;

            protected override void OnItemRecycle(TM item)
            {
                if (item == null
                    || item.gameObject == null
                    || MonoPoolRoot.instance == null
                    || MonoPoolRoot.instance.transform == null)
                {
                    return;
                }

                item.transform.SetParent(MonoPoolRoot.instance.transform, false);
                item.gameObject.SetActive(false);
            }

            public override TM Pop()
            {
                TM item = base.Pop();
                while (item == null || item.gameObject == null)
                {
                    item = base.Pop();
                }
                return item;
            }

            public override void Dispose()
            {
                base.Dispose();
                if (tempPrefab != null)
                {
                    UnityEngine.Object.Destroy(tempPrefab);
                }
            }
        }

        protected Dictionary<string, MPool<T>> pools = new Dictionary<string, MPool<T>>();

        /// <summary>
        /// 创建对象池
        /// 注：不需要时需要请方法Dispose(),不然占用的内存永远不会释放
        /// </summary>
        public MonoPool()
        {
            tName = typeof(T).Name;
            MonoPoolRoot.instance.RegisterPool(this);
        }

        public bool IsInited()
        {
            return IsInited(tName);
        }

        public bool IsInited(string prefabName)
        {
            return pools.ContainsKey(prefabName);
        }

        /// <summary>
        /// 初始化对象池
        /// </summary>
        /// <param name="directory">prefab相对Resources的路径</param>
        public MonoPool<T> Init(string directory)
        {
            return Init(tName, directory);
        }

        /// <summary>
        /// 初始化对象池
        /// </summary>
        /// <param name="prefabName">prefab名字</param>
        /// <param name="directory">prefab相对Resources的路径</param>
        public MonoPool<T> Init(string prefabName, string directory)
        {
            if (!pools.ContainsKey(prefabName))
            {
                directory = $"{directory}/{prefabName}";
                GameObject prefab = Resources.Load<GameObject>(directory);
                if (prefab == null)
                {
                    throw new ArgumentNullException($"[{prefabName}] {directory} prefab is null");
                }
                Func<T> creatFunc = () => UnityEngine.Object.Instantiate<GameObject>(prefab).Name(prefab.name).GetComponent<T>();
                MPool<T> pool = new MPool<T>();
                pool.Init(creatFunc);
                pools.Add(prefabName, pool);
            }
            return this;
        }

        /// <summary>
        /// 初始化对象池
        /// </summary>
        public MonoPool<T> Init(T prefab)
        {
            string name = prefab.name;

            if (!pools.ContainsKey(name))
            {
                T newPrefab = UnityEngine.Object.Instantiate<T>(prefab).Name(prefab.name);
                newPrefab.transform.SetParent(MonoPoolRoot.instance.transform, false);
                newPrefab.gameObject.SetActive(false);

                Func<T> creatFunc = () => UnityEngine.Object.Instantiate<T>(newPrefab).Name(newPrefab.name);
                MPool<T> pool = new MPool<T>();
                pool.tempPrefab = newPrefab.gameObject;
                pool.Init(creatFunc);
                pools.Add(name, pool);
            }
            return this;
        }

        public MonoPool<T> Prepare(int count)
        {
            return Prepare(tName, count);
        }

        public MonoPool<T> Prepare(string prefabName, int count)
        {
            if (pools.ContainsKey(prefabName))
            {
                pools[prefabName].Prepare(count);
            }
            else
            {
                LogError($"[Prepare] {prefabName}, this pool is uninit");
            }
            return this;
        }

        object IPool.Pop()
        {
            return this.Pop();
        }

        bool IPool.Push(object item)
        {
            return this.Push(item as T);
        }

        public T Pop()
        {
            return Pop(tName);
        }

        public T Pop(string prefabName)
        {
            if (pools.ContainsKey(prefabName))
            {
                return pools[prefabName].Pop();
            }
            else
            {
                string error = $"[Pop] {prefabName}, this pool is uninit";
                LogError(error);
                throw new NullReferenceException(error);
            }
        }

        public bool Push(T item)
        {
            if (item == null || item.isRecycled)
            {
                return false;
            }

            string name = item.name;
            if (pools.ContainsKey(name))
            {
                return pools[name].Push(item);
            }
            else
            {
                string error = $"[Push] {name}, this pool is uninit";
                LogError(error);
                throw new NullReferenceException(error);
            }
        }

        /// <summary>
        /// 删除池中所有缓存对象
        /// </summary>
        public void DestroyAllItemCache()
        {
            foreach (var pool in pools)
            {
                pool.Value.DestroyAllItemCache();
            }
        }

        private void Log(string info)
        {
            Debug.Log($"[MonoPool][{tName}] {info}");
        }

        private void LogError(string info)
        {
            Debug.Log($"[MonoPool][{tName}] {info}");
        }

        public void ClearAllSubPools()
        {
            foreach (var pool in pools)
            {
                pool.Value.Dispose();
            }
            pools.Clear();
        }

        public void Dispose()
        {
            foreach (var pool in pools)
            {
                pool.Value.Dispose();
            }
            pools.Clear();
            if (_instance == this)
            {
                _instance = null;
            }
            MonoPoolRoot.instance.DeregisterPool(this);
        }
    }
}
